#!/usr/bin/env python3
"""
Schema Validation and Enforcement for GSM Extension System

Provides comprehensive validation of extension configurations, solver parameters,
and system requirements with detailed error reporting and auto-correction suggestions.
"""

import json
import re
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple, Union
import yaml
from packaging import version
import logging


class ValidationLevel(Enum):
    """Validation severity levels."""
    ERROR = "error"
    WARNING = "warning" 
    INFO = "info"
    DEBUG = "debug"


@dataclass
class ValidationIssue:
    """Individual validation issue."""
    level: ValidationLevel
    path: str
    message: str
    suggestion: Optional[str] = None
    code: Optional[str] = None
    
    def __str__(self) -> str:
        result = f"[{self.level.value.upper()}] {self.path}: {self.message}"
        if self.suggestion:
            result += f"\n  Suggestion: {self.suggestion}"
        return result


@dataclass
class ValidationResult:
    """Result of configuration validation."""
    valid: bool
    issues: List[ValidationIssue] = field(default_factory=list)
    corrected_config: Optional[Dict[str, Any]] = None
    
    @property
    def errors(self) -> List[ValidationIssue]:
        """Get only error-level issues."""
        return [issue for issue in self.issues if issue.level == ValidationLevel.ERROR]
    
    @property
    def warnings(self) -> List[ValidationIssue]:
        """Get only warning-level issues.""" 
        return [issue for issue in self.issues if issue.level == ValidationLevel.WARNING]
    
    def add_issue(self, level: ValidationLevel, path: str, message: str, 
                  suggestion: Optional[str] = None, code: Optional[str] = None):
        """Add a validation issue."""
        self.issues.append(ValidationIssue(level, path, message, suggestion, code))
        if level == ValidationLevel.ERROR:
            self.valid = False


class SchemaValidator:
    """Configuration schema validator with auto-correction capabilities."""
    
    def __init__(self):
        self.logger = logging.getLogger(__name__)
        self._load_schemas()
    
    def _load_schemas(self):
        """Load built-in validation schemas."""
        self.extension_schema = {
            "type": "object",
            "required": ["name", "version", "type"],
            "properties": {
                "name": {
                    "type": "string",
                    "pattern": r"^[a-z][a-z0-9_]*$",
                    "description": "Extension name (lowercase, alphanumeric, underscores)"
                },
                "version": {
                    "type": "string", 
                    "pattern": r"^\d+\.\d+\.\d+(-[a-zA-Z0-9]+)?$",
                    "description": "Semantic version (e.g., 1.0.0)"
                },
                "type": {
                    "type": "string",
                    "enum": ["config-driven", "python-based"],
                    "description": "Extension implementation type"
                },
                "description": {
                    "type": "string",
                    "minLength": 10,
                    "description": "Human-readable description"
                },
                "supported_frameworks": {
                    "type": "array",
                    "items": {"type": "string"},
                    "minItems": 1,
                    "description": "List of supported solver frameworks"
                },
                "priority": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100,
                    "default": 50,
                    "description": "Extension priority (0-100)"
                },
                "dependencies": {
                    "type": "object",
                    "properties": {
                        "system": {"type": "array", "items": {"$ref": "#/definitions/dependency"}},
                        "python": {"type": "array", "items": {"$ref": "#/definitions/dependency"}},
                        "build_tools": {"type": "array", "items": {"type": "string"}},
                        "libraries": {"type": "array", "items": {"$ref": "#/definitions/dependency"}},
                        "runtime": {"type": "array", "items": {"$ref": "#/definitions/dependency"}}
                    }
                },
                "build": {
                    "type": "object", 
                    "properties": {
                        "type": {
                            "type": "string",
                            "enum": ["cmake", "make", "ninja", "custom"]
                        },
                        "commands": {"type": "array", "items": {"type": "string"}},
                        "parallel": {"type": "boolean", "default": True},
                        "environment": {"type": "object"}
                    }
                },
                "test": {
                    "type": "object",
                    "properties": {
                        "commands": {"type": "array", "items": {"type": "string"}},
                        "timeout": {"type": "integer", "minimum": 1, "default": 300},
                        "parallel": {"type": "boolean", "default": False}
                    }
                },
                "detection": {
                    "type": "object",
                    "properties": {
                        "patterns": {
                            "type": "array",
                            "items": {"$ref": "#/definitions/detection_pattern"}
                        },
                        "confidence_threshold": {
                            "type": "number",
                            "minimum": 0.0,
                            "maximum": 1.0,
                            "default": 0.7
                        }
                    }
                }
            },
            "definitions": {
                "dependency": {
                    "type": "object",
                    "required": ["name"],
                    "properties": {
                        "name": {"type": "string"},
                        "version": {"type": "string"},
                        "type": {
                            "type": "string",
                            "enum": ["system", "python", "library", "tool", "runtime"]
                        },
                        "optional": {"type": "boolean", "default": False}
                    }
                },
                "detection_pattern": {
                    "type": "object",
                    "required": ["type", "pattern"],
                    "properties": {
                        "type": {
                            "type": "string", 
                            "enum": ["xpath", "yaml_path", "content", "filename"]
                        },
                        "pattern": {"type": "string"},
                        "expected": {"type": "string"},
                        "confidence": {"type": "number", "minimum": 0.0, "maximum": 1.0}
                    }
                }
            }
        }
    
    def validate_extension_config(self, config: Dict[str, Any], 
                                 auto_correct: bool = False) -> ValidationResult:
        """Validate extension configuration against schema."""
        result = ValidationResult(valid=True)
        corrected = config.copy() if auto_correct else None
        
        # Validate against JSON schema
        self._validate_schema(config, self.extension_schema, result, "", corrected)
        
        # Additional semantic validations
        self._validate_extension_semantics(config, result, corrected)
        
        if corrected and result.issues:
            result.corrected_config = corrected
            
        return result
    
    def _validate_schema(self, data: Any, schema: Dict[str, Any], 
                        result: ValidationResult, path: str, corrected: Optional[Dict] = None):
        """Validate data against JSON schema."""
        if "type" in schema:
            self._validate_type(data, schema, result, path, corrected)
        
        if "required" in schema and isinstance(data, dict):
            self._validate_required(data, schema["required"], result, path, corrected)
        
        if "properties" in schema and isinstance(data, dict):
            self._validate_properties(data, schema["properties"], result, path, corrected)
        
        if "items" in schema and isinstance(data, list):
            self._validate_array_items(data, schema["items"], result, path, corrected)
    
    def _validate_type(self, data: Any, schema: Dict[str, Any], 
                      result: ValidationResult, path: str, corrected: Optional[Dict] = None):
        """Validate data type."""
        expected_type = schema["type"]
        
        type_map = {
            "string": str,
            "integer": int,
            "number": (int, float),
            "boolean": bool,
            "array": list,
            "object": dict
        }
        
        if expected_type in type_map:
            expected_python_type = type_map[expected_type]
            if not isinstance(data, expected_python_type):
                # Attempt auto-correction
                if corrected is not None and expected_type == "string":
                    # Convert to string
                    if path:
                        self._set_nested_value(corrected, path, str(data))
                        result.add_issue(ValidationLevel.WARNING, path,
                                       f"Auto-converted {type(data).__name__} to string")
                else:
                    result.add_issue(ValidationLevel.ERROR, path,
                                   f"Expected {expected_type}, got {type(data).__name__}")
    
    def _validate_required(self, data: Dict[str, Any], required: List[str],
                          result: ValidationResult, path: str, corrected: Optional[Dict] = None):
        """Validate required fields."""
        for field in required:
            if field not in data:
                field_path = f"{path}.{field}" if path else field
                
                # Auto-correction: add default values
                if corrected is not None:
                    default_value = self._get_default_value(field)
                    if default_value is not None:
                        data[field] = default_value
                        result.add_issue(ValidationLevel.WARNING, field_path,
                                       f"Added default value: {default_value}")
                    else:
                        result.add_issue(ValidationLevel.ERROR, field_path,
                                       "Required field missing")
                else:
                    result.add_issue(ValidationLevel.ERROR, field_path,
                                   "Required field missing")
    
    def _validate_properties(self, data: Dict[str, Any], properties: Dict[str, Any],
                           result: ValidationResult, path: str, corrected: Optional[Dict] = None):
        """Validate object properties."""
        for prop, prop_schema in properties.items():
            prop_path = f"{path}.{prop}" if path else prop
            
            if prop in data:
                self._validate_schema(data[prop], prop_schema, result, prop_path, corrected)
            elif "default" in prop_schema and corrected is not None:
                data[prop] = prop_schema["default"]
                result.add_issue(ValidationLevel.INFO, prop_path,
                               f"Added default value: {prop_schema['default']}")
    
    def _validate_array_items(self, data: List[Any], item_schema: Dict[str, Any],
                            result: ValidationResult, path: str, corrected: Optional[Dict] = None):
        """Validate array items."""
        for i, item in enumerate(data):
            item_path = f"{path}[{i}]"
            self._validate_schema(item, item_schema, result, item_path, corrected)
    
    def _validate_extension_semantics(self, config: Dict[str, Any],
                                    result: ValidationResult, corrected: Optional[Dict] = None):
        """Perform semantic validation beyond schema."""
        # Validate version format
        if "version" in config:
            try:
                version.Version(config["version"])
            except version.InvalidVersion:
                result.add_issue(ValidationLevel.ERROR, "version",
                               "Invalid semantic version format",
                               suggestion="Use format: MAJOR.MINOR.PATCH (e.g., 1.0.0)")
        
        # Validate name format
        if "name" in config:
            name = config["name"]
            if not re.match(r'^[a-z][a-z0-9_]*$', name):
                result.add_issue(ValidationLevel.ERROR, "name",
                               "Name must start with lowercase letter and contain only lowercase letters, numbers, and underscores")
        
        # Validate framework consistency
        if "supported_frameworks" in config and "detection" in config:
            frameworks = set(config["supported_frameworks"])
            detection_patterns = config["detection"].get("patterns", [])
            
            # Check if detection patterns align with supported frameworks
            if detection_patterns and not any(self._pattern_matches_frameworks(pattern, frameworks) 
                                            for pattern in detection_patterns):
                result.add_issue(ValidationLevel.WARNING, "detection.patterns",
                               "Detection patterns may not match supported frameworks")
        
        # Validate build configuration
        if "build" in config:
            build_config = config["build"]
            if "type" in build_config and "commands" in build_config:
                build_type = build_config["type"]
                commands = build_config["commands"]
                
                if build_type == "cmake" and not any("cmake" in cmd for cmd in commands):
                    result.add_issue(ValidationLevel.WARNING, "build.commands",
                                   "Build type is 'cmake' but no cmake commands found")
    
    def _pattern_matches_frameworks(self, pattern: Dict[str, Any], 
                                   frameworks: Set[str]) -> bool:
        """Check if detection pattern is relevant to supported frameworks."""
        pattern_text = pattern.get("pattern", "").lower()
        expected = pattern.get("expected", "").lower()
        
        for framework in frameworks:
            if framework.lower() in pattern_text or framework.lower() in expected:
                return True
        
        return False
    
    def _get_default_value(self, field: str) -> Any:
        """Get default value for a field."""
        defaults = {
            "type": "config-driven",
            "priority": 50,
            "description": "Auto-generated extension",
            "supported_frameworks": [],
            "dependencies": {},
            "build": {"type": "cmake", "commands": [], "parallel": True},
            "test": {"commands": [], "timeout": 300},
            "detection": {"patterns": [], "confidence_threshold": 0.7}
        }
        return defaults.get(field)
    
    def _set_nested_value(self, data: Dict[str, Any], path: str, value: Any):
        """Set value at nested path in dictionary."""
        parts = path.split(".")
        current = data
        
        for part in parts[:-1]:
            if part not in current:
                current[part] = {}
            current = current[part]
        
        current[parts[-1]] = value


class ConfigurationValidator:
    """High-level configuration validator for multiple file types."""
    
    def __init__(self):
        self.schema_validator = SchemaValidator()
        self.logger = logging.getLogger(__name__)
    
    def validate_file(self, config_path: Path, auto_correct: bool = False) -> ValidationResult:
        """Validate configuration file."""
        try:
            # Load configuration
            config = self._load_config_file(config_path)
            
            # Determine validation type
            if config_path.name.endswith(('extension.yaml', 'extension.yml')):
                return self.schema_validator.validate_extension_config(config, auto_correct)
            elif config_path.suffix in ['.xml']:
                return self._validate_xml_solver_config(config_path)
            elif config_path.suffix in ['.yaml', '.yml']:
                return self._validate_yaml_solver_config(config)
            else:
                result = ValidationResult(valid=False)
                result.add_issue(ValidationLevel.ERROR, str(config_path),
                               "Unsupported configuration file format")
                return result
                
        except Exception as e:
            result = ValidationResult(valid=False)
            result.add_issue(ValidationLevel.ERROR, str(config_path),
                           f"Failed to validate configuration: {e}")
            return result
    
    def _load_config_file(self, config_path: Path) -> Dict[str, Any]:
        """Load configuration from file."""
        with open(config_path, 'r') as f:
            if config_path.suffix in ['.yaml', '.yml']:
                return yaml.safe_load(f) or {}
            elif config_path.suffix == '.json':
                return json.load(f)
            else:
                raise ValueError(f"Unsupported file format: {config_path.suffix}")
    
    def _validate_xml_solver_config(self, config_path: Path) -> ValidationResult:
        """Validate XML solver configuration."""
        result = ValidationResult(valid=True)
        
        try:
            import xml.etree.ElementTree as ET
            tree = ET.parse(config_path)
            root = tree.getroot()
            
            # Basic XML structure validation
            if root.tag != "ParameterList":
                result.add_issue(ValidationLevel.ERROR, "root",
                               "XML root element must be 'ParameterList'")
            
            # Check for required solver parameters
            self._validate_xml_solver_params(root, result)
            
        except ET.ParseError as e:
            result.add_issue(ValidationLevel.ERROR, str(config_path),
                           f"XML parsing error: {e}")
        
        return result
    
    def _validate_xml_solver_params(self, root, result: ValidationResult):
        """Validate XML solver parameters."""
        required_params = ["solver_type", "max_iterations", "convergence_tol"]
        found_params = set()
        
        for param in root.iter("Parameter"):
            name = param.get("name")
            if name:
                found_params.add(name)
                
                # Validate parameter values
                param_type = param.get("type")
                value = param.get("value")
                
                if name == "max_iterations" and param_type == "int":
                    try:
                        val = int(value)
                        if val <= 0:
                            result.add_issue(ValidationLevel.WARNING, f"Parameter[{name}]",
                                           "max_iterations should be positive")
                    except (ValueError, TypeError):
                        result.add_issue(ValidationLevel.ERROR, f"Parameter[{name}]",
                                       "max_iterations must be an integer")
                
                elif name == "convergence_tol" and param_type in ["double", "float"]:
                    try:
                        val = float(value)
                        if val <= 0:
                            result.add_issue(ValidationLevel.WARNING, f"Parameter[{name}]",
                                           "convergence_tol should be positive")
                    except (ValueError, TypeError):
                        result.add_issue(ValidationLevel.ERROR, f"Parameter[{name}]",
                                       "convergence_tol must be a number")
        
        # Check for missing required parameters
        missing = set(required_params) - found_params
        for param in missing:
            result.add_issue(ValidationLevel.WARNING, "Parameters",
                           f"Recommended parameter '{param}' not found")
    
    def _validate_yaml_solver_config(self, config: Dict[str, Any]) -> ValidationResult:
        """Validate YAML solver configuration."""
        result = ValidationResult(valid=True)
        
        # Check basic structure
        if "solver" not in config:
            result.add_issue(ValidationLevel.ERROR, "solver",
                           "Missing 'solver' section")
        else:
            solver_config = config["solver"]
            
            # Validate solver type
            if "type" not in solver_config:
                result.add_issue(ValidationLevel.ERROR, "solver.type",
                               "Missing solver type")
            
            # Validate numerical parameters
            if "max_iterations" in solver_config:
                try:
                    val = int(solver_config["max_iterations"])
                    if val <= 0:
                        result.add_issue(ValidationLevel.WARNING, "solver.max_iterations",
                                       "max_iterations should be positive")
                except (ValueError, TypeError):
                    result.add_issue(ValidationLevel.ERROR, "solver.max_iterations",
                                   "max_iterations must be an integer")
        
        return result


def main():
    """Command-line interface for configuration validation."""
    import argparse
    
    parser = argparse.ArgumentParser(description="Validate GSM configuration files")
    parser.add_argument("config_path", type=Path, help="Path to configuration file")
    parser.add_argument("--auto-correct", action="store_true", 
                       help="Attempt to auto-correct issues")
    parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
    
    args = parser.parse_args()
    
    if args.verbose:
        logging.basicConfig(level=logging.INFO)
    
    validator = ConfigurationValidator()
    result = validator.validate_file(args.config_path, args.auto_correct)
    
    if result.valid:
        print(f"✅ Configuration is valid: {args.config_path}")
    else:
        print(f"❌ Configuration has errors: {args.config_path}")
    
    for issue in result.issues:
        print(f"  {issue}")
    
    if result.corrected_config and args.auto_correct:
        corrected_path = args.config_path.with_suffix(".corrected" + args.config_path.suffix)
        with open(corrected_path, 'w') as f:
            yaml.dump(result.corrected_config, f, default_flow_style=False)
        print(f"📝 Auto-corrected configuration saved to: {corrected_path}")
    
    return 0 if result.valid else 1


if __name__ == "__main__":
    exit(main())